#include <mc/player.h>
#include <mc/world.h>
#include <mc/chunk.h>
#include <math/OOB.h>
#include <util/tools.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <iostream>

namespace mc
{

// 玩家眼睛到脚的距离（要扣掉半格）
#define PLAYER_EYE_HEIGHT 1.7f
// 玩家身高
#define PLAYER_HEIGHT 2.0f
// 玩家半径
#define PLAYER_RADIUS 0.3f

// 跳跃速度
#define JUMP_SPEED 6.0f

// 放置方块的判断间隔
#define PLACE_INTERVAL 0.1f

World* world = util::Singleton<World>::instance();

//! @brief 构造函数
Player::Player(const glm::vec3& position) : 
    Camera(glm::vec3(position.x, position.y + PLAYER_EYE_HEIGHT, position.z)),
    _chunkID(Chunk::getChunkID(util::ROUND(position.x), util::ROUND(position.z))),
    _jumping(false), _jumpTargetPosY(0.0f), 
    _flying(true), _sneaking(false),
    _falling(false), _fallingSpeed(0.0f),
    _holdingBlock(BlockType::STONE),
    _handBlock(BlockType::STONE)
    {
#ifdef DEBUG
        std::cout << "player status: " << "flying: " << this->_flying << ", sneaking: " << this->_sneaking << '\n';
#endif
    }

Player::~Player() noexcept 
{
#ifdef DEBUG
    std::cout << "free player\n";
#endif
}

//! @brief 绘制
void Player::draw()
{
    Block::prepareRender(this->viewMat());

    glm::mat4 model = glm::mat4(1.0f);
    model = glm::translate(model, this->_position + this->_front);
    model = glm::scale(model, glm::vec3(0.4f, 0.4f, 0.4f));

    this->_handBlock.firstPersonViewDraw(model);
}

glm::vec3 getPos(const glm::vec3& pos)
{
    return glm::vec3(util::ROUND(pos.x) + 0.5, util::ROUND(pos.y) + 0.5, util::ROUND(pos.z) + 0.5);
}

bool collideWithBlokc(const math::OOB& oob, const glm::vec3& pos)
{   
    return oob.collide( math::OOB(pos, glm::vec2(0.5f, 0.0f), glm::vec2(0.0f, 0.5f), 0.5f));
}

bool Player::collide()
{
    math::OOB oob(this->_nextPosition, this->_front, this->_right, PLAYER_HEIGHT / 2);
    
    if (!world->isEmptyBlock(this->_nextPosition) && collideWithBlokc(oob, getPos(this->_nextPosition)))
        return true;

    if (!world->isEmptyBlock(this->_nextPosition + glm::vec3(1.0f, 0.0f, 0.0f)) && collideWithBlokc(oob, getPos(this->_nextPosition + glm::vec3(1.0f, 0.0f, 1.0f))))
        return true;
    if (!world->isEmptyBlock(this->_nextPosition + glm::vec3(1.0f, -1.0f, 0.0f)) && collideWithBlokc(oob, getPos(this->_nextPosition + glm::vec3(1.0f, -1.0f, 1.0f))))
        return true;

    if (!world->isEmptyBlock(this->_nextPosition + glm::vec3(-1.0f, 0.0f, 0.0f)) && collideWithBlokc(oob, getPos(this->_nextPosition + glm::vec3(1.0f, 0.0f, 1.0f))))
        return true;
    if (!world->isEmptyBlock(this->_nextPosition + glm::vec3(-1.0f, -1.0f, 0.0f)) && collideWithBlokc(oob, getPos(this->_nextPosition + glm::vec3(1.0f, -1.0f, 1.0f))))
        return true;

    if (!world->isEmptyBlock(this->_nextPosition + glm::vec3(0.0f, 0.0f, 1.0f)) && collideWithBlokc(oob, getPos(this->_nextPosition + glm::vec3(1.0f, 0.0f, 1.0f))))
        return true;
    if (!world->isEmptyBlock(this->_nextPosition + glm::vec3(0.0f, -1.0f, -1.0f)) && collideWithBlokc(oob, getPos(this->_nextPosition + glm::vec3(1.0f, -1.0f, 1.0f))))
        return true;

    if (!world->isEmptyBlock(this->_nextPosition + glm::vec3(0.0f, 0.0f, 1.0f)) && collideWithBlokc(oob, getPos(this->_nextPosition + glm::vec3(1.0f, 0.0f, 1.0f))))
        return true;
    if (!world->isEmptyBlock(this->_nextPosition + glm::vec3(0.0f, -1.0f, -1.0f)) && collideWithBlokc(oob, getPos(this->_nextPosition + glm::vec3(1.0f, -1.0f, 1.0f))))
        return true;

    return false;

}

//! @brief 玩家移动
void Player::move(opengl::CameraMovement direction, float deltatime)
{
    glm::vec3 checkPos = this->horizontalMove(direction, deltatime, PLAYER_RADIUS);

    // 潜行不能下落！
    if (this->_sneaking && world->isEmptyBlock(checkPos.x, checkPos.y - 2.0f, checkPos.z))
        return;
        
    // 前放方块是否为空
    if (world->isEmptyBlock(checkPos) && world->isEmptyBlock(checkPos - glm::vec3(0.0f, 1.0f, 0.0f)))
        this->updatePosition();

    // 进入新区块？
    ChunkID newChunkId = Chunk::getChunkID(util::ROUND(this->_position.x), util::ROUND(this->_position.z));
    if (newChunkId != this->_chunkID)
    {
        // 更新世界区块！
#ifdef DEBUG
        std::cout << "update chunks!\n";
#endif
        world->updateChunks(this->_chunkID, newChunkId);
        this->_chunkID = newChunkId;
    }
}

//! @brief 玩家脚坐标（标准化）
glm::vec3 Player::footPosition() const
{
    glm::vec3 pos = this->_position;
    pos.y -= (PLAYER_EYE_HEIGHT - 0.1f);
    return util::ROUND(pos);
}

//! @brief 玩家头坐标（标准化）
glm::vec3 Player::headPosition() const
{
    return util::ROUND(this->_position);
}

#define G 10.0f

//! @brief 玩家下落
void Player::fall(float deltatime)
{
    // 判断脚下的方块是否为 Empty
    if (world->isEmptyBlock(this->_position.x, this->_position.y - PLAYER_EYE_HEIGHT, this->_position.z))
    {
        this->_falling = true;
        float lastFaillingSpeed = this->_fallingSpeed;
        this->_fallingSpeed += G * deltatime;
        this->_position.y -= deltatime * (this->_fallingSpeed + lastFaillingSpeed) / 2.0f;
    }
    else
    {
        this->_falling = false;
        this->_fallingSpeed = 0.0f;
    }
}

//! @brief 玩家跳跃
void Player::jump(float deltatime)
{
    // 飞行模式不跳跃
    if (this->_flying)
    {
        this->rise(deltatime);
        return;
    }

    // 潜行不跳
    if (this->_sneaking)
        return;
        
    // 头顶不能有方块！
    if (!this->_falling && !this->_jumping && world->isEmptyBlock(this->_position.x, this->_position.y - PLAYER_EYE_HEIGHT + 2, this->_position.z))
    {
        this->_jumping = true;
        this->_jumpTargetPosY = this->jumpTargetPosY();
    }
}

//! @brief 玩家潜行走
void Player::sneak(float deltaTime)
{
    // 如果在飞行，潜行就是下落
    if (this->_flying)
        this->drop(deltaTime);
}

//! @brief 更新玩家状态与位置
void Player::update(float deltatime)
{
    // 飞行模式与潜行模式不跳跃、不下落
    if (this->_flying || this->_sneaking)
        return;

    // 正在跳跃
    if (this->_jumping)
    {
        glm::vec3& pos = this->position();
        if (pos.y < this->_jumpTargetPosY)
            pos.y += JUMP_SPEED * deltatime;
        else
        {
            pos.y = this->_jumpTargetPosY;
            this->_jumping = false;
            this->_falling = true;
        }
    }

    // 不跳跃，判断下落
    else this->fall(deltatime);
}

//! @brief 更改手中的物品
void Player::changeHodingBlock(char direction)
{
    if (this->_holdingBlock == BlockType::EMPTY && direction == -1)
        this->_holdingBlock = static_cast<BlockType>(BlockType::BLOCKTYEP_COUNT - 1);
    else if (this->_holdingBlock == (BlockType::BLOCKTYEP_COUNT - 1) && direction == 1)
        this->_holdingBlock = BlockType::EMPTY;
    else 
        this->_holdingBlock = static_cast<BlockType>(this->_holdingBlock + direction);
    
    this->_handBlock.switchBlock(this->_holdingBlock);
    std::cout << "current block: " << this->_holdingBlock << std::endl;
}

//! @brief 放置方块
void Player::placeBlock()
{
    if (this->_holdingBlock == BlockType::EMPTY)
        return;

    // 获得指向方块
    glm::vec3 placePos(0.0f, 0.0f, 0.0f);
    if (this->pointingEmptyBlock(placePos))
    {
        world->placeBlock(placePos, this->_holdingBlock);
    }
}

//! @brief 移除方块
void Player::removeBlock()
{
    // 获得指向方块
    glm::vec3 pointingPos(0.0f, 0.0f, 0.0f);
    if (this->pointingBlock(pointingPos))
    {
        world->removeBlock(pointingPos);
    }
}

//! @brief 切换运动方式
void Player::switchMovingWay()
{
    // 潜行模式不能切换飞行
    if (this->_sneaking)
        return;
    this->_flying = !this->_flying;
#ifdef DEBUG
    std::cout << "flying status: " << this->_flying << '\n';
#endif
}

//! @brief 设置是否潜行
void Player::setSneaking(bool enable)
{ 
    // 飞行模式无法切换
    if (this->_flying)
        return;
    this->_sneaking = enable; 
#ifdef DEBUG
    std::cout << "sneaking status: " << this->_sneaking << '\n';
#endif
}

//! @brief 获得玩家指向的坐标
bool Player::pointingEmptyBlock(glm::vec3& pos) const
{
    // 玩家的头脚坐标
    glm::vec3 footPos = this->footPosition();
    glm::vec3 headPos = this->headPosition();

    for (float i = 0.0f; i < 50.0f; i += 2.0f)
    {
        glm::vec3 placePos = this->_position + this->_front * (i * PLACE_INTERVAL);
        glm::vec3 roundPlacePos = util::ROUND(placePos);

        // 不能放在头或脚上
        if (roundPlacePos == footPos || roundPlacePos == headPos)
            continue;
        
        // 找到一个空方块
        if (world->isEmptyBlock(placePos))
        {
            // 找其相邻的下一个方块
            for (float j = i + 1.0f; j < 50.0f; j += 1.0f)
            {
                glm::vec3 nextPlacePos = this->_position + this->_front * (j * PLACE_INTERVAL);
                // 放置位置必须与某个方块相邻
                if (util::isBlockClosed(util::ROUND(nextPlacePos), roundPlacePos))
                {
                    if (!world->isEmptyBlock(nextPlacePos))
                    {
                        // 找到！
                        pos = roundPlacePos;
                        return true;
                    }
                    // 找到了相邻的下一关方块，但其是空，这个方块不可以放置，打破这个循环
                    else 
                        break;
                }
            }
        }
    }

    return false;
}

//! @brief 获得玩家指向的坐标
bool Player::pointingBlock(glm::vec3& pos) const
{
    for (float i = 0.0f; i < 50.0f; i += 2.0f)
    {
        glm::vec3 placePos = this->_position + this->_front * (i * PLACE_INTERVAL);
        glm::vec3 roundPlacePos = util::ROUND(placePos);
        
        // 找到一个空方块
        if (world->isEmptyBlock(placePos))
        {
            // 找其相邻的下一个方块
            for (float j = i + 1.0f; j < 50.0f; j += 1.0f)
            {
                glm::vec3 nextPlacePos = this->_position + this->_front * (j * PLACE_INTERVAL);
                // 放置位置必须与某个方块相邻
                glm::vec3 nextRoundPlacePos = util::ROUND(nextPlacePos);
                if (util::isBlockClosed(nextRoundPlacePos, roundPlacePos))
                {
                    if (!world->isEmptyBlock(nextPlacePos))
                    {
                        // 找到！
                        pos = nextRoundPlacePos;
                        return true;
                    }
                    // 找到了相邻的下一关方块，但其是空，这个方块不可以放置，打破这个循环
                    else 
                        break;
                }
            }
        }
    }

    return false;
}

}